/**
 * 
 */
package pacman.map;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.tiled.TiledMap;
import org.newdawn.slick.util.pathfinding.AStarPathFinder;
import org.newdawn.slick.util.pathfinding.Mover;
import org.newdawn.slick.util.pathfinding.Path;
import org.newdawn.slick.util.pathfinding.PathFindingContext;
import org.newdawn.slick.util.pathfinding.TileBasedMap;

import pacman.actors.Block;
import pacman.actors.Brain;
import pacman.actors.EatGem;
import pacman.actors.Gem;
import pacman.actors.Ghost;
import pacman.actors.Player;
import pacman.base.Body;
import pacman.base.Entity;
import pacman.base.Level;
import pacman.brains.BlueGhostBrain;
import pacman.brains.GrayGhostBrain;
import pacman.brains.GreenGhostBrain;
import pacman.brains.OrangeGhostBrain;
import pacman.brains.PinkGhostBrain;
import pacman.brains.RedGhostBrain;

public class Map implements Entity, TileBasedMap {
    private TiledMap map;													//Lưu Map cho mỗi Level
    private static Player player;											//Dùng để Load thông tin về Player - Pacman từ map
    public enum LAYER {														//Các Layer của Map
    	terrain, entity, bonus
    };
    public static final int SIZE = 32;										//Size của một tile
    private boolean[][] blocked;											//Dùng 1 mảng 2 chiều biểu thị các tile của Map
    private Iterable<Body> blockingEnt = null;						
    private Iterable<Body> collectableEnt = null;
    private Iterable<Body> eatGemEnt = null;
    private Iterable<Body> ghostEnt;
    private Hashtable<String, Vector2f> bluePoint;
    private Vector2f base;
    private Vector2f cornerUpLeft = new Vector2f(1000, 1000);
    private Vector2f cornerUpRight = new Vector2f(0, 0);
    private Vector2f cornerDownLeft = new Vector2f(0, 1000);
    private Vector2f cornerDownRight = new Vector2f(0, 0);

    public Map(String mapPath) throws SlickException {
		map = new TiledMap(mapPath);
		player = loadPlayer();
		blocked = getCollisionMatrix(map, "blocked", "false");
		blockingEnt = loadBlockingEntities(LAYER.terrain, "blocked");
		collectableEnt = loadGemEntities(LAYER.bonus, "gem");
		eatGemEnt = loadEatGemEntities(LAYER.bonus, "eatGem");
		bluePoint = loadBluePoint(LAYER.entity, "position");
		ghostEnt = loadGhostEntities(LAYER.entity, "ghost");
		base = loadBaseEntity(Map.LAYER.entity, "base");
		initCorners();
    }
    
    public void render(BasicGameState game, Graphics g) {
    	map.render(0, 0, 0, 0, SIZE, SIZE, LAYER.terrain.ordinal(), false);
    }

    public void update(GameContainer game, int delta) {				//một Map thì không cần Update gì hết
    }

    private Player loadPlayer() throws SlickException {											//Load thông tin về player ở trên map
		Player pl = null;
		try {
		    pl = new Player(this, Role.PLAYER, getPlayerStart(Map.LAYER.entity, "player"));
		} catch (SlickException e) {
		    throw new SlickException("cannot find player position " + e.getMessage());
		}
		return pl;
    }
    
    public Shape getPlayerStart(LAYER layer, String prop) {
    	Shape player = null;
    	for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
    	    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
	    		int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
	    		String value = map.getTileProperty(tileID, prop, "false");
	    		if ("true".equals(value)) {
	    		    int blockSize = map.getTileHeight();
	    		    int xrec = xAxis * blockSize;
	    		    int yrec = yAxis * blockSize;
	    		    Rectangle rect = new Rectangle(xrec, yrec, blockSize, blockSize);
	    		    player = rect;
	    		}
    	    }
    	}
    	return player;
    }
    
    private boolean[][] getCollisionMatrix(TiledMap map, String key, String value) {
  		boolean[][] matrix = new boolean[map.getWidth()][map.getHeight()];
  		for (int x = 0; x < map.getWidth(); x++) {
  		    for (int y = 0; y < map.getHeight(); y++) {
	  			int tileID = map.getTileId(x, y, 0);
	  			String temp = map.getTileProperty(tileID, key, value);
	  			if ("true".equals(temp)) {
	  			    matrix[x][y] = true;
	  			}
  		    }
  		}
  		return matrix;
      }

    private Iterable<Body> loadBlockingEntities(LAYER layer, String prop) {
		Vector<Body> ent = new Vector<Body>();
		for (int xAxis=0; xAxis<map.getWidth(); xAxis++) {
		    for (int yAxis=0; yAxis<map.getHeight(); yAxis++) {
			int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
			String value = map.getTileProperty(tileID, prop, "false");
			if ("true".equals(value)) {
			    int blockSize = map.getTileHeight();
			    int xrec = xAxis * blockSize;
			    int yrec = yAxis * blockSize;
			    Rectangle rect = new Rectangle(xrec, yrec, blockSize,
				    blockSize);
			    // a block is a logical entity, without graphical
			    // reprensetation
			    Block block = new Block(Role.BLOCK, rect);
			    ent.add(block);
				}
		    }
		}
		return ent;
    }
    
    private Iterable<Body> loadGemEntities(LAYER layer, String prop) {
    	Vector<Body> ent = new Vector<Body>();

    	for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
    	    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
	    		int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
	    		// blocked
	    		String value = map.getTileProperty(tileID, prop, "false");
	    		if ("true".equals(value)) {
	    		    int blockSize = map.getTileHeight();
	    		    int xrec = xAxis * blockSize;
	    		    int yrec = yAxis * blockSize;
	    		    Rectangle rect = new Rectangle(xrec, yrec, blockSize,
	    			    blockSize);
	    		    Image gem = map.getTileImage(xAxis, yAxis, layer.ordinal());
	    		    Gem block = new Gem(Role.GOLD, rect, gem);
	    		    ent.add(block);
	    		}
    	    }
    	}
    	return ent;
    }

    private Iterable<Body> loadEatGemEntities(LAYER layer, String prop) {
		Vector<Body> ent = new Vector<Body>();
	
		for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
		    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
				int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
				// blocked
				String value = map.getTileProperty(tileID, prop, "false");
				if ("true".equals(value)) {
				    int blockSize = map.getTileHeight();
				    int xrec = xAxis * blockSize;
				    int yrec = yAxis * blockSize;
				    Rectangle rect = new Rectangle(xrec, yrec, blockSize,
					    blockSize);
				    Image gem = map.getTileImage(xAxis, yAxis, layer.ordinal());
				    EatGem block = new EatGem(Role.EATGEM, rect, gem);
				    ent.add(block);
				}
		    }
		}
		return ent;
    }

    private Iterable<Body> loadGhostEntities(LAYER layer, String prop) {
		Vector<Body> ent = new Vector<Body>();
	
		for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
		    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {					//Check Từng Tile Trên map
				int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());				//Lấy TileID ra
				String value = map.getTileProperty(tileID, prop, "false");				//Kiểm tra nếu tile có Property là "Ghost"
				if ("true".equals(value)) {												//Nếu có thì
				    int blockSize = map.getTileHeight();		
				    int xrec = xAxis * blockSize;
				    int yrec = yAxis * blockSize;
				    Rectangle rect = new Rectangle(xrec, yrec, blockSize, blockSize);	//Tạo Shape để check Collision
				    Ghost ghost = null;													//Tạo một thực thể Ghost
				    try {																//Kiểm tra màu để gán Brain - tức là AI của nó
						String color = map.getTileProperty(tileID, "red", "false");		//Nếu là màu đỏ
						Brain b = null;													//Tạo một thực thể Brain
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new RedGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 0);
						}
						color = map.getTileProperty(tileID, "pink", "false");
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new PinkGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 2);
						}
						color = map.getTileProperty(tileID, "blue", "false");
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new BlueGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 3);
						}
						color = map.getTileProperty(tileID, "orange", "false");
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new OrangeGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 1);
						}
						color = map.getTileProperty(tileID, "green", "false");
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new GreenGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 6);
						}
						color = map.getTileProperty(tileID, "gray", "false");
						if (color.equals("true")) {
						    Vector2f pos = new Vector2f();
						    pos.set(xAxis * 32, yAxis * 32);
						    b = new GrayGhostBrain(this, pos);
						    ghost = new Ghost(this, Role.GHOST, rect, b, 7);
						    ((GrayGhostBrain) ghost.getBrain()).setParent(ghost);
						}
				    } catch (Exception e) {
				    	System.out.println(e.getMessage());
				    }
				    ent.add(ghost);
				}
		    }
		}
		return ent;
    }

    private Hashtable<String, Vector2f> loadBluePoint(LAYER layer, String prop) {
		Hashtable<String, Vector2f> positions = new Hashtable<String, Vector2f>();
		for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
		    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
				int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if ("true".equals(value)) {
				    int blockSize = map.getTileHeight();
				    int xrec = xAxis * blockSize;
				    int yrec = yAxis * blockSize;
		
				    String name = map.getTileProperty(tileID, "a", "false");
				    if (name.equals("true")) positions.put("a", new Vector2f(xrec, yrec));
				    name = map.getTileProperty(tileID, "b", "false");
				    if (name.equals("true")) positions.put("b", new Vector2f(xrec, yrec));
				    name = map.getTileProperty(tileID, "c", "false");
				    if (name.equals("true")) positions.put("c", new Vector2f(xrec, yrec));
				    name = map.getTileProperty(tileID, "d", "false");
				    if (name.equals("true")) positions.put("d", new Vector2f(xrec, yrec));
				}
		    }
		}
		return positions;
    }

    private Vector2f loadBaseEntity(LAYER layer, String prop) {
		Vector2f base = new Vector2f(0, 0);
		for (int xAxis = 0; xAxis < map.getWidth(); xAxis++) {
		    for (int yAxis = 0; yAxis < map.getHeight(); yAxis++) {
				int tileID = map.getTileId(xAxis, yAxis, layer.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if ("true".equals(value)) {
				    base = new Vector2f(xAxis, yAxis);
				    return base;
				}
		    }
		}
		return base;
    }
    /**
     * Add tất cả Entites của một Map vào trong Level
     */
    public void addToLevel(Level l) {
    	//Những vật thể có thể thu thập được trên bản đồ
		Vector<Body> collectable = (Vector<Body>) getCollectableEnt();						//Mọi thứ bên trong một map đều là Body, 
		for (Iterator<Body> iterator = collectable.iterator(); iterator.hasNext();) {		//tức là chúng có hình dạng và Role
		    Body bod = iterator.next();														//Chạy từng Body trong Array
		    bod.addToLevel(l);																//Add vào Level
		    l.add(bod);																		//Add vào Array Entity		
		}
		// Những thứ có thể ăn được																	
		Vector<Body> eatableGem = (Vector<Body>) getEatGemEnt();							//Load Array
		for (Iterator<Body> iterator = eatableGem.iterator(); iterator.hasNext();) {
		    Body bod = iterator.next();														//Chạy Từng thành phần của Array		
		    bod.addToLevel(l);																//Add vào level
		    l.add(bod);																		//Add vào Vector<Entity>
		}
		// Add Người chơi vào Level - tức là Pacman
		l.add(player);																		//Add vào Entity
		// adding ghosts entities loaded from map
		Vector<Body> ghosts = (Vector<Body>) getGhostEnt();									//Load đám Ghost ở trên map vào				
		for (Iterator<Body> iterator = ghosts.iterator(); iterator.hasNext();) {
		    Body bod = iterator.next();
		    bod.addToLevel(l);															
		    l.add(bod);
		}
    }

    public Role getRole() {
	return Entity.Role.MAP;
    }

    public void onCollision(Entity obstacle) {
	// cannot collide with anyone
    }

    public void removeFromLevel(Level l) {
    }


    public Iterable<Body> getEatGemEnt() {
      	return eatGemEnt;
    }
    
    public Iterable<Body> getBlockingEntities() {
    	return blockingEnt;
    }

   
    public Iterable<Body> getCollectableEnt() {
    	return collectableEnt;
    }
    
    public Iterable<Body> getGhostEnt() {
    	return ghostEnt;
    }

    public static Player getPlayer() {
    	return player;
    }

    public boolean isToRemove() {
    	return false;
    }

    public Path getUpdatedPath(int sx, int sy, int ex, int ey) throws NullPointerException {
		Path path;
		AStarPathFinder pathfinder = new AStarPathFinder(this, 1000, false);
		Mover dummyMover = new Mover() {
		};
		path = pathfinder.findPath(dummyMover, sx, sy, ex, ey);
		if (path != null) {
		    return path;
		} else {
		    // return null;
		    throw new NullPointerException("cannot find a path");
		}
    }

    public boolean blocked(PathFindingContext contex, int x, int y) {
		if (blocked == null) {
		    blocked = getCollisionMatrix(map, "blocked", "false");
		}
		try {
		    if (blocked[x][y]) return true;
		} catch (ArrayIndexOutOfBoundsException e) {
		    return true;
		}
			return false;
    }

    public float getCost(PathFindingContext contex, int x, int y) {
    	return 0;
    }

    public int getHeightInTiles() {
    	return map.getHeight();
    }

    public int getWidthInTiles() {
    	return map.getWidth();
    }

    public void pathFinderVisited(int x, int y) {

    }

  
    
    private void initCorners() {
		boolean first = true;
		for (int x = 0; x < map.getWidth(); x++) {
		    for (int y = 0; y < map.getHeight(); y++) {
			boolean block = blocked[x][y];
			if (!block) {
			    if (first) {
					first = false;
					cornerUpLeft.set(x, y);
				    }
				    if (x >= cornerUpRight.x && y > cornerUpRight.y) {
					cornerUpRight.set(x, y);
				    }
				    if (x >= cornerDownLeft.x && y <= cornerDownLeft.y) {
					cornerDownLeft.set(x, y);
				    }
				    if (x >= cornerDownRight.x && y >= cornerDownRight.y) {
					cornerDownRight.set(x, y);
			    }
			}
		    }
		}
    }

    /**
     * @return Return a random corner from map
     */
    public Vector2f getRandomCorner() {
		Vector2f corner = new Vector2f(0, 0);
		Random rnd = new Random();
		int value = rnd.nextInt(3);
		switch (value) {
		case 0:
		    corner.set(cornerUpLeft);
		    break;
		case 1:
		    corner.set(cornerUpRight);
		    break;
		case 2:
		    corner.set(cornerDownLeft);
		    break;
		case 3:
		    corner.set(cornerDownRight);
		    break;
		default:
		    corner.set(cornerUpLeft);
		    break;
		}
	
		return corner;
    }
    
    public Vector2f getRandomPoint() {
		Vector2f point = new Vector2f();
		Random rnd = new Random();
		boolean found = false;
		while (!found) {
		    int rndX = rnd.nextInt(map.getWidth() - 1);
		    int rndY = rnd.nextInt(map.getHeight() - 1);
	
		    if (!blocked[rndX][rndY]) {
				point.x = rndX * 32;
				point.y = rndY * 32;
				found = true;
		    }
		}
		return point;
    }
    public Vector2f getBase() {
    	return base;
    }
    public Hashtable<String, Vector2f> getBluePoint() {
    	return bluePoint;
    }

    public int getTileSize() {
    	return map.getTileHeight();
    }
}
